package com.sloan.community.service;

import com.sloan.community.common.Constant;
import com.sloan.community.common.vo.QuestionVO;
import com.sloan.community.common.vo.TagVO;
import com.sloan.community.exception.CommunityException;
import com.sloan.community.po.Article;
import com.sloan.community.po.ArticleContent;
import com.sloan.community.po.ArticleLikeCount;
import com.sloan.community.po.User;
import com.sloan.community.repository.*;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.util.HashSet;
import java.util.Objects;

@Slf4j
@Service
@RequiredArgsConstructor(onConstructor_ = {@Lazy})
public class QuestionService {

    private final ArticleRepository articleRepository;

    private final UserRepository userRepository;

    private final ArticleContentRepository articleContentRepository;

    private final TagRepository tagRepository;

    private final ArticleTagRepository articleTagRepository;

    private final LikeCountRepository likeCountRepository;

    public Mono<QuestionVO> questionDetail(String username, Long articleId, User sessionUser) {
        Mono<QuestionVO> questionMono = articleRepository.findById(articleId)
                .switchIfEmpty(Constant.articleNotFoundException(articleId))
                .flatMap(article -> {

                    Mono<ArticleContent> contentMono = articleContentRepository.findByArticleId(articleId);
                    Mono<User> userMono = userRepository.findById(article.getUserId());
                    Flux<TagVO> tagFlux = articleTagRepository.findByArticleId(articleId)
                            .flatMap(articleTag -> {
                                return tagRepository.findById(articleTag.getTagId())
                                        .flatMap(tag -> {
                                            TagVO tagVO = new TagVO();
                                            BeanUtils.copyProperties(tag, tagVO);
                                            return Mono.just(tagVO);
                                        });
                            });

                    Flux<Long> likeCountUserIdFlux = likeCountRepository.findByArticleId(articleId)
                            .map(ArticleLikeCount::getUserId).distinct();

                    return Mono.zip(contentMono, userMono, tagFlux.collectList(),
                                    likeCountUserIdFlux.collectList())
                            .flatMap(tuple -> {
                                ArticleContent content = tuple.getT1();
                                User user = tuple.getT2();

                                QuestionVO question = new QuestionVO();
                                BeanUtils.copyProperties(article, question);
                                question.setArticleId(article.getId());
                                question.setContent(content.getContent());
                                question.setUsername(user.getUsername());
                                question.setNickname(user.getNickname());
                                question.setAvatarUrl(user.getAvatarUrl());
                                question.setAuthFrom(user.getAuthFrom());
                                question.setFromT(Constant.Oauth2.getByCode(user.getAuthFrom()).getMsg());
                                question.setAddress(user.getAddress());
                                question.setTags(tuple.getT3());
                                question.setSessionUserLike(Objects.nonNull(sessionUser)
                                        && new HashSet<>(tuple.getT4()).contains(sessionUser.getId()));
                                return Mono.just(question);
                            });
                });
        Mono<Void> voidMono = articleRepository.updateViewByArticleId(articleId);
        return voidMono.then(questionMono);
    }

    @Transactional(rollbackFor = Exception.class)
    public Mono<Long> articleLikeCount(Long articleId, Long userId) {
        Mono<Article> articleMono = articleRepository.findById(articleId)
                .switchIfEmpty(Constant.articleNotFoundException(articleId));

        Flux<Long> likeUserIdFlux = likeCountRepository.findByArticleId(articleId)
                .switchIfEmpty(Mono.empty())
                .map(ArticleLikeCount::getUserId).distinct();

        return Mono.when(articleMono)
                .thenMany(likeUserIdFlux)
                .filter(likeUserId -> likeUserId.equals(userId))
                .count()
                .flatMap(count -> {
                    if (count == 0) {
                        return likeCount(articleId, userId)
                                .then(articleMono.map(Article::getLikeCount));
                    }
                    return unLikeCount(articleId, userId)
                            .then(articleMono.map(Article::getLikeCount));
                });
    }

    public Mono<ArticleLikeCount> likeCount(Long articleId, Long userId) {
        Mono<Void> voidMono = articleRepository.likeCount(articleId);
        ArticleLikeCount articleLikeCount = new ArticleLikeCount();
        articleLikeCount.setUserId(userId);
        articleLikeCount.setArticleId(articleId);
        Mono<ArticleLikeCount> save = likeCountRepository.save(articleLikeCount);
        return voidMono.then(save);
    }

    public Mono<Void> unLikeCount(Long articleId, Long userId) {
        Mono<Void> voidMono = articleRepository.unLikeCount(articleId);
        Mono<Void> mono = likeCountRepository.deleteByArticleIdAndUserId(articleId, userId);
        return voidMono.then(mono);
    }

    public Mono<Long> articleShareCount(Long articleId) {
        return articleRepository.updateShareCountById(articleId)
                .thenReturn(articleId);
    }
}
